1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.base;
18  
19  import static com.google.common.base.CharMatcher.BREAKING_WHITESPACE;
20  import static com.google.common.base.CharMatcher.WHITESPACE;
21  import static com.google.common.base.CharMatcher.anyOf;
22  import static com.google.common.base.CharMatcher.forPredicate;
23  import static com.google.common.base.CharMatcher.inRange;
24  import static com.google.common.base.CharMatcher.is;
25  import static com.google.common.base.CharMatcher.isNot;
26  import static com.google.common.base.CharMatcher.noneOf;
27  
28  import com.google.common.annotations.GwtCompatible;
29  import com.google.common.collect.Sets;
30  
31  import junit.framework.AssertionFailedError;
32  import junit.framework.TestCase;
33  
34  import java.util.Arrays;
35  import java.util.HashSet;
36  import java.util.Random;
37  import java.util.Set;
38  
39  /**
40   * Unit test for {@link CharMatcher}.
41   *
42   * @author Kevin Bourrillion
43   */
44  @GwtCompatible(emulated = true)
45  public class CharMatcherTest extends TestCase {
46  
47    private static final CharMatcher WHATEVER = new CharMatcher() {
48      @Override public boolean matches(char c) {
49        throw new AssertionFailedError(
50            "You weren't supposed to actually invoke me!");
51      }
52    };
53  
54    public void testAnyAndNone_logicalOps() throws Exception {
55      // These are testing behavior that's never promised by the API, but since
56      // we're lucky enough that these do pass, it saves us from having to write
57      // more excruciating tests! Hooray!
58  
59      assertSame(CharMatcher.ANY, CharMatcher.NONE.negate());
60      assertSame(CharMatcher.NONE, CharMatcher.ANY.negate());
61  
62      assertSame(WHATEVER, CharMatcher.ANY.and(WHATEVER));
63      assertSame(CharMatcher.ANY, CharMatcher.ANY.or(WHATEVER));
64  
65      assertSame(CharMatcher.NONE, CharMatcher.NONE.and(WHATEVER));
66      assertSame(WHATEVER, CharMatcher.NONE.or(WHATEVER));
67    }
68  
69    // The rest of the behavior of ANY and DEFAULT will be covered in the tests for
70    // the text processing methods below.
71  
72    public void testWhitespaceBreakingWhitespaceSubset() throws Exception {
73      for (int c = 0; c <= Character.MAX_VALUE; c++) {
74        if (BREAKING_WHITESPACE.apply((char) c)) {
75          assertTrue(Integer.toHexString(c), WHITESPACE.apply((char) c));
76        }
77      }
78    }
79  
80    // The next tests require ICU4J and have, at least for now, been sliced out
81    // of the open-source view of the tests.
82  
83    // Omitting tests for the rest of the JAVA_* constants as these are defined
84    // as extremely straightforward pass-throughs to the JDK methods.
85  
86    // We're testing the is(), isNot(), anyOf(), noneOf() and inRange() methods
87    // below by testing their text-processing methods.
88  
89    // The organization of this test class is unusual, as it's not done by
90    // method, but by overall "scenario". Also, the variety of actual tests we
91    // do borders on absurd overkill. Better safe than sorry, though?
92  
93    public void testEmpty() throws Exception {
94      doTestEmpty(CharMatcher.ANY);
95      doTestEmpty(CharMatcher.NONE);
96      doTestEmpty(is('a'));
97      doTestEmpty(isNot('a'));
98      doTestEmpty(anyOf(""));
99      doTestEmpty(anyOf("x"));
100     doTestEmpty(anyOf("xy"));
101     doTestEmpty(anyOf("CharMatcher"));
102     doTestEmpty(noneOf("CharMatcher"));
103     doTestEmpty(inRange('n', 'q'));
104     doTestEmpty(forPredicate(Predicates.equalTo('c')));
105   }
106 
107   private void doTestEmpty(CharMatcher matcher) throws Exception {
108     reallyTestEmpty(matcher);
109     reallyTestEmpty(matcher.negate());
110     reallyTestEmpty(matcher.precomputed());
111   }
112 
113   private void reallyTestEmpty(CharMatcher matcher) throws Exception {
114     assertEquals(-1, matcher.indexIn(""));
115     assertEquals(-1, matcher.indexIn("", 0));
116     try {
117       matcher.indexIn("", 1);
118       fail();
119     } catch (IndexOutOfBoundsException expected) {
120     }
121     try {
122       matcher.indexIn("", -1);
123       fail();
124     } catch (IndexOutOfBoundsException expected) {
125     }
126     assertEquals(-1, matcher.lastIndexIn(""));
127     assertFalse(matcher.matchesAnyOf(""));
128     assertTrue(matcher.matchesAllOf(""));
129     assertTrue(matcher.matchesNoneOf(""));
130     assertEquals("", matcher.removeFrom(""));
131     assertEquals("", matcher.replaceFrom("", 'z'));
132     assertEquals("", matcher.replaceFrom("", "ZZ"));
133     assertEquals("", matcher.trimFrom(""));
134     assertEquals(0, matcher.countIn(""));
135   }
136 
137   public void testNoMatches() {
138     doTestNoMatches(CharMatcher.NONE, "blah");
139     doTestNoMatches(is('a'), "bcde");
140     doTestNoMatches(isNot('a'), "aaaa");
141     doTestNoMatches(anyOf(""), "abcd");
142     doTestNoMatches(anyOf("x"), "abcd");
143     doTestNoMatches(anyOf("xy"), "abcd");
144     doTestNoMatches(anyOf("CharMatcher"), "zxqy");
145     doTestNoMatches(noneOf("CharMatcher"), "ChMa");
146     doTestNoMatches(inRange('p', 'x'), "mom");
147     doTestNoMatches(forPredicate(Predicates.equalTo('c')), "abe");
148     doTestNoMatches(inRange('A', 'Z').and(inRange('F', 'K').negate()), "F1a");
149     doTestNoMatches(CharMatcher.DIGIT, "\tAz()");
150     doTestNoMatches(CharMatcher.JAVA_DIGIT, "\tAz()");
151     doTestNoMatches(CharMatcher.DIGIT.and(CharMatcher.ASCII), "\tAz()");
152     doTestNoMatches(CharMatcher.SINGLE_WIDTH, "\u05bf\u3000");
153   }
154 
155   private void doTestNoMatches(CharMatcher matcher, String s) {
156     reallyTestNoMatches(matcher, s);
157     reallyTestAllMatches(matcher.negate(), s);
158     reallyTestNoMatches(matcher.precomputed(), s);
159     reallyTestAllMatches(matcher.negate().precomputed(), s);
160     reallyTestAllMatches(matcher.precomputed().negate(), s);
161     reallyTestNoMatches(forPredicate(matcher), s);
162 
163     reallyTestNoMatches(matcher, new StringBuilder(s));
164   }
165 
166   public void testAllMatches() {
167     doTestAllMatches(CharMatcher.ANY, "blah");
168     doTestAllMatches(isNot('a'), "bcde");
169     doTestAllMatches(is('a'), "aaaa");
170     doTestAllMatches(noneOf("CharMatcher"), "zxqy");
171     doTestAllMatches(anyOf("x"), "xxxx");
172     doTestAllMatches(anyOf("xy"), "xyyx");
173     doTestAllMatches(anyOf("CharMatcher"), "ChMa");
174     doTestAllMatches(inRange('m', 'p'), "mom");
175     doTestAllMatches(forPredicate(Predicates.equalTo('c')), "ccc");
176     doTestAllMatches(CharMatcher.DIGIT, "0123456789\u0ED0\u1B59");
177     doTestAllMatches(CharMatcher.JAVA_DIGIT, "0123456789");
178     doTestAllMatches(CharMatcher.DIGIT.and(CharMatcher.ASCII), "0123456789");
179     doTestAllMatches(CharMatcher.SINGLE_WIDTH, "\t0123ABCdef~\u00A0\u2111");
180   }
181 
182   private void doTestAllMatches(CharMatcher matcher, String s) {
183     reallyTestAllMatches(matcher, s);
184     reallyTestNoMatches(matcher.negate(), s);
185     reallyTestAllMatches(matcher.precomputed(), s);
186     reallyTestNoMatches(matcher.negate().precomputed(), s);
187     reallyTestNoMatches(matcher.precomputed().negate(), s);
188     reallyTestAllMatches(forPredicate(matcher), s);
189 
190     reallyTestAllMatches(matcher, new StringBuilder(s));
191   }
192 
193   private void reallyTestNoMatches(CharMatcher matcher, CharSequence s) {
194     assertFalse(matcher.matches(s.charAt(0)));
195     assertEquals(-1, matcher.indexIn(s));
196     assertEquals(-1, matcher.indexIn(s, 0));
197     assertEquals(-1, matcher.indexIn(s, 1));
198     assertEquals(-1, matcher.indexIn(s, s.length()));
199     try {
200       matcher.indexIn(s, s.length() + 1);
201       fail();
202     } catch (IndexOutOfBoundsException expected) {
203     }
204     try {
205       matcher.indexIn(s, -1);
206       fail();
207     } catch (IndexOutOfBoundsException expected) {
208     }
209     assertEquals(-1, matcher.lastIndexIn(s));
210     assertFalse(matcher.matchesAnyOf(s));
211     assertFalse(matcher.matchesAllOf(s));
212     assertTrue(matcher.matchesNoneOf(s));
213 
214     assertEquals(s.toString(), matcher.removeFrom(s));
215     assertEquals(s.toString(), matcher.replaceFrom(s, 'z'));
216     assertEquals(s.toString(), matcher.replaceFrom(s, "ZZ"));
217     assertEquals(s.toString(), matcher.trimFrom(s));
218     assertEquals(0, matcher.countIn(s));
219   }
220 
221   private void reallyTestAllMatches(CharMatcher matcher, CharSequence s) {
222     assertTrue(matcher.matches(s.charAt(0)));
223     assertEquals(0, matcher.indexIn(s));
224     assertEquals(0, matcher.indexIn(s, 0));
225     assertEquals(1, matcher.indexIn(s, 1));
226     assertEquals(-1, matcher.indexIn(s, s.length()));
227     assertEquals(s.length() - 1, matcher.lastIndexIn(s));
228     assertTrue(matcher.matchesAnyOf(s));
229     assertTrue(matcher.matchesAllOf(s));
230     assertFalse(matcher.matchesNoneOf(s));
231     assertEquals("", matcher.removeFrom(s));
232     assertEquals(Strings.repeat("z", s.length()),
233         matcher.replaceFrom(s, 'z'));
234     assertEquals(Strings.repeat("ZZ", s.length()),
235         matcher.replaceFrom(s, "ZZ"));
236     assertEquals("", matcher.trimFrom(s));
237     assertEquals(s.length(), matcher.countIn(s));
238   }
239 
240   public void testGeneral() {
241     doTestGeneral(is('a'), 'a', 'b');
242     doTestGeneral(isNot('a'), 'b', 'a');
243     doTestGeneral(anyOf("x"), 'x', 'z');
244     doTestGeneral(anyOf("xy"), 'y', 'z');
245     doTestGeneral(anyOf("CharMatcher"), 'C', 'z');
246     doTestGeneral(noneOf("CharMatcher"), 'z', 'C');
247     doTestGeneral(inRange('p', 'x'), 'q', 'z');
248   }
249 
250   private void doTestGeneral(CharMatcher matcher, char match, char noMatch) {
251     doTestOneCharMatch(matcher, "" + match);
252     doTestOneCharNoMatch(matcher, "" + noMatch);
253     doTestMatchThenNoMatch(matcher, "" + match + noMatch);
254     doTestNoMatchThenMatch(matcher, "" + noMatch + match);
255   }
256 
257   private void doTestOneCharMatch(CharMatcher matcher, String s) {
258     reallyTestOneCharMatch(matcher, s);
259     reallyTestOneCharNoMatch(matcher.negate(), s);
260     reallyTestOneCharMatch(matcher.precomputed(), s);
261     reallyTestOneCharNoMatch(matcher.negate().precomputed(), s);
262     reallyTestOneCharNoMatch(matcher.precomputed().negate(), s);
263   }
264 
265   private void doTestOneCharNoMatch(CharMatcher matcher, String s) {
266     reallyTestOneCharNoMatch(matcher, s);
267     reallyTestOneCharMatch(matcher.negate(), s);
268     reallyTestOneCharNoMatch(matcher.precomputed(), s);
269     reallyTestOneCharMatch(matcher.negate().precomputed(), s);
270     reallyTestOneCharMatch(matcher.precomputed().negate(), s);
271   }
272 
273   private void doTestMatchThenNoMatch(CharMatcher matcher, String s) {
274     reallyTestMatchThenNoMatch(matcher, s);
275     reallyTestNoMatchThenMatch(matcher.negate(), s);
276     reallyTestMatchThenNoMatch(matcher.precomputed(), s);
277     reallyTestNoMatchThenMatch(matcher.negate().precomputed(), s);
278     reallyTestNoMatchThenMatch(matcher.precomputed().negate(), s);
279   }
280 
281   private void doTestNoMatchThenMatch(CharMatcher matcher, String s) {
282     reallyTestNoMatchThenMatch(matcher, s);
283     reallyTestMatchThenNoMatch(matcher.negate(), s);
284     reallyTestNoMatchThenMatch(matcher.precomputed(), s);
285     reallyTestMatchThenNoMatch(matcher.negate().precomputed(), s);
286     reallyTestMatchThenNoMatch(matcher.precomputed().negate(), s);
287   }
288 
289   private void reallyTestOneCharMatch(CharMatcher matcher, String s) {
290     assertTrue(matcher.matches(s.charAt(0)));
291     assertTrue(matcher.apply(s.charAt(0)));
292     assertEquals(0, matcher.indexIn(s));
293     assertEquals(0, matcher.indexIn(s, 0));
294     assertEquals(-1, matcher.indexIn(s, 1));
295     assertEquals(0, matcher.lastIndexIn(s));
296     assertTrue(matcher.matchesAnyOf(s));
297     assertTrue(matcher.matchesAllOf(s));
298     assertFalse(matcher.matchesNoneOf(s));
299     assertEquals("", matcher.removeFrom(s));
300     assertEquals("z", matcher.replaceFrom(s, 'z'));
301     assertEquals("ZZ", matcher.replaceFrom(s, "ZZ"));
302     assertEquals("", matcher.trimFrom(s));
303     assertEquals(1, matcher.countIn(s));
304   }
305 
306   private void reallyTestOneCharNoMatch(CharMatcher matcher, String s) {
307     assertFalse(matcher.matches(s.charAt(0)));
308     assertFalse(matcher.apply(s.charAt(0)));
309     assertEquals(-1, matcher.indexIn(s));
310     assertEquals(-1, matcher.indexIn(s, 0));
311     assertEquals(-1, matcher.indexIn(s, 1));
312     assertEquals(-1, matcher.lastIndexIn(s));
313     assertFalse(matcher.matchesAnyOf(s));
314     assertFalse(matcher.matchesAllOf(s));
315     assertTrue(matcher.matchesNoneOf(s));
316 
317     assertSame(s, matcher.removeFrom(s));
318     assertSame(s, matcher.replaceFrom(s, 'z'));
319     assertSame(s, matcher.replaceFrom(s, "ZZ"));
320     assertSame(s, matcher.trimFrom(s));
321     assertSame(0, matcher.countIn(s));
322   }
323 
324   private void reallyTestMatchThenNoMatch(CharMatcher matcher, String s) {
325     assertEquals(0, matcher.indexIn(s));
326     assertEquals(0, matcher.indexIn(s, 0));
327     assertEquals(-1, matcher.indexIn(s, 1));
328     assertEquals(-1, matcher.indexIn(s, 2));
329     assertEquals(0, matcher.lastIndexIn(s));
330     assertTrue(matcher.matchesAnyOf(s));
331     assertFalse(matcher.matchesAllOf(s));
332     assertFalse(matcher.matchesNoneOf(s));
333     assertEquals(s.substring(1), matcher.removeFrom(s));
334     assertEquals("z" + s.substring(1), matcher.replaceFrom(s, 'z'));
335     assertEquals("ZZ" + s.substring(1), matcher.replaceFrom(s, "ZZ"));
336     assertEquals(s.substring(1), matcher.trimFrom(s));
337     assertEquals(1, matcher.countIn(s));
338   }
339 
340   private void reallyTestNoMatchThenMatch(CharMatcher matcher, String s) {
341     assertEquals(1, matcher.indexIn(s));
342     assertEquals(1, matcher.indexIn(s, 0));
343     assertEquals(1, matcher.indexIn(s, 1));
344     assertEquals(-1, matcher.indexIn(s, 2));
345     assertEquals(1, matcher.lastIndexIn(s));
346     assertTrue(matcher.matchesAnyOf(s));
347     assertFalse(matcher.matchesAllOf(s));
348     assertFalse(matcher.matchesNoneOf(s));
349     assertEquals(s.substring(0, 1), matcher.removeFrom(s));
350     assertEquals(s.substring(0, 1) + "z", matcher.replaceFrom(s, 'z'));
351     assertEquals(s.substring(0, 1) + "ZZ", matcher.replaceFrom(s, "ZZ"));
352     assertEquals(s.substring(0, 1), matcher.trimFrom(s));
353     assertEquals(1, matcher.countIn(s));
354   }
355 
356   /**
357    * Checks that expected is equals to out, and further, if in is
358    * equals to expected, then out is successfully optimized to be
359    * identical to in, i.e. that "in" is simply returned.
360    */
361   private void assertEqualsSame(String expected, String in, String out) {
362     if (expected.equals(in)) {
363       assertSame(in, out);
364     } else {
365       assertEquals(expected, out);
366     }
367   }
368 
369   // Test collapse() a little differently than the rest, as we really want to
370   // cover lots of different configurations of input text
371   public void testCollapse() {
372     // collapsing groups of '-' into '_' or '-'
373     doTestCollapse("-", "_");
374     doTestCollapse("x-", "x_");
375     doTestCollapse("-x", "_x");
376     doTestCollapse("--", "_");
377     doTestCollapse("x--", "x_");
378     doTestCollapse("--x", "_x");
379     doTestCollapse("-x-", "_x_");
380     doTestCollapse("x-x", "x_x");
381     doTestCollapse("---", "_");
382     doTestCollapse("--x-", "_x_");
383     doTestCollapse("--xx", "_xx");
384     doTestCollapse("-x--", "_x_");
385     doTestCollapse("-x-x", "_x_x");
386     doTestCollapse("-xx-", "_xx_");
387     doTestCollapse("x--x", "x_x");
388     doTestCollapse("x-x-", "x_x_");
389     doTestCollapse("x-xx", "x_xx");
390     doTestCollapse("x-x--xx---x----x", "x_x_xx_x_x");
391 
392     doTestCollapseWithNoChange("");
393     doTestCollapseWithNoChange("x");
394     doTestCollapseWithNoChange("xx");
395   }
396 
397   private void doTestCollapse(String in, String out) {
398     // Try a few different matchers which all match '-' and not 'x'
399     // Try replacement chars that both do and do not change the value.
400     for (char replacement : new char[] { '_', '-' }) {
401       String expected = out.replace('_', replacement);
402       assertEqualsSame(expected, in, is('-').collapseFrom(in, replacement));
403       assertEqualsSame(expected, in, is('-').collapseFrom(in, replacement));
404       assertEqualsSame(expected, in, is('-').or(is('#')).collapseFrom(in, replacement));
405       assertEqualsSame(expected, in, isNot('x').collapseFrom(in, replacement));
406       assertEqualsSame(expected, in, is('x').negate().collapseFrom(in, replacement));
407       assertEqualsSame(expected, in, anyOf("-").collapseFrom(in, replacement));
408       assertEqualsSame(expected, in, anyOf("-#").collapseFrom(in, replacement));
409       assertEqualsSame(expected, in, anyOf("-#123").collapseFrom(in, replacement));
410     }
411   }
412 
413   private void doTestCollapseWithNoChange(String inout) {
414     assertSame(inout, is('-').collapseFrom(inout, '_'));
415     assertSame(inout, is('-').or(is('#')).collapseFrom(inout, '_'));
416     assertSame(inout, isNot('x').collapseFrom(inout, '_'));
417     assertSame(inout, is('x').negate().collapseFrom(inout, '_'));
418     assertSame(inout, anyOf("-").collapseFrom(inout, '_'));
419     assertSame(inout, anyOf("-#").collapseFrom(inout, '_'));
420     assertSame(inout, anyOf("-#123").collapseFrom(inout, '_'));
421     assertSame(inout, CharMatcher.NONE.collapseFrom(inout, '_'));
422   }
423 
424   public void testCollapse_any() {
425     assertEquals("", CharMatcher.ANY.collapseFrom("", '_'));
426     assertEquals("_", CharMatcher.ANY.collapseFrom("a", '_'));
427     assertEquals("_", CharMatcher.ANY.collapseFrom("ab", '_'));
428     assertEquals("_", CharMatcher.ANY.collapseFrom("abcd", '_'));
429   }
430 
431   public void testTrimFrom() {
432     // trimming -
433     doTestTrimFrom("-", "");
434     doTestTrimFrom("x-", "x");
435     doTestTrimFrom("-x", "x");
436     doTestTrimFrom("--", "");
437     doTestTrimFrom("x--", "x");
438     doTestTrimFrom("--x", "x");
439     doTestTrimFrom("-x-", "x");
440     doTestTrimFrom("x-x", "x-x");
441     doTestTrimFrom("---", "");
442     doTestTrimFrom("--x-", "x");
443     doTestTrimFrom("--xx", "xx");
444     doTestTrimFrom("-x--", "x");
445     doTestTrimFrom("-x-x", "x-x");
446     doTestTrimFrom("-xx-", "xx");
447     doTestTrimFrom("x--x", "x--x");
448     doTestTrimFrom("x-x-", "x-x");
449     doTestTrimFrom("x-xx", "x-xx");
450     doTestTrimFrom("x-x--xx---x----x", "x-x--xx---x----x");
451     // additional testing using the doc example
452     assertEquals("cat", anyOf("ab").trimFrom("abacatbab"));
453   }
454 
455   private void doTestTrimFrom(String in, String out) {
456     // Try a few different matchers which all match '-' and not 'x'
457     assertEquals(out, is('-').trimFrom(in));
458     assertEquals(out, is('-').or(is('#')).trimFrom(in));
459     assertEquals(out, isNot('x').trimFrom(in));
460     assertEquals(out, is('x').negate().trimFrom(in));
461     assertEquals(out, anyOf("-").trimFrom(in));
462     assertEquals(out, anyOf("-#").trimFrom(in));
463     assertEquals(out, anyOf("-#123").trimFrom(in));
464   }
465 
466   public void testTrimLeadingFrom() {
467     // trimming -
468     doTestTrimLeadingFrom("-", "");
469     doTestTrimLeadingFrom("x-", "x-");
470     doTestTrimLeadingFrom("-x", "x");
471     doTestTrimLeadingFrom("--", "");
472     doTestTrimLeadingFrom("x--", "x--");
473     doTestTrimLeadingFrom("--x", "x");
474     doTestTrimLeadingFrom("-x-", "x-");
475     doTestTrimLeadingFrom("x-x", "x-x");
476     doTestTrimLeadingFrom("---", "");
477     doTestTrimLeadingFrom("--x-", "x-");
478     doTestTrimLeadingFrom("--xx", "xx");
479     doTestTrimLeadingFrom("-x--", "x--");
480     doTestTrimLeadingFrom("-x-x", "x-x");
481     doTestTrimLeadingFrom("-xx-", "xx-");
482     doTestTrimLeadingFrom("x--x", "x--x");
483     doTestTrimLeadingFrom("x-x-", "x-x-");
484     doTestTrimLeadingFrom("x-xx", "x-xx");
485     doTestTrimLeadingFrom("x-x--xx---x----x", "x-x--xx---x----x");
486     // additional testing using the doc example
487     assertEquals("catbab", anyOf("ab").trimLeadingFrom("abacatbab"));
488   }
489 
490   private void doTestTrimLeadingFrom(String in, String out) {
491     // Try a few different matchers which all match '-' and not 'x'
492     assertEquals(out, is('-').trimLeadingFrom(in));
493     assertEquals(out, is('-').or(is('#')).trimLeadingFrom(in));
494     assertEquals(out, isNot('x').trimLeadingFrom(in));
495     assertEquals(out, is('x').negate().trimLeadingFrom(in));
496     assertEquals(out, anyOf("-#").trimLeadingFrom(in));
497     assertEquals(out, anyOf("-#123").trimLeadingFrom(in));
498   }
499 
500   public void testTrimTrailingFrom() {
501     // trimming -
502     doTestTrimTrailingFrom("-", "");
503     doTestTrimTrailingFrom("x-", "x");
504     doTestTrimTrailingFrom("-x", "-x");
505     doTestTrimTrailingFrom("--", "");
506     doTestTrimTrailingFrom("x--", "x");
507     doTestTrimTrailingFrom("--x", "--x");
508     doTestTrimTrailingFrom("-x-", "-x");
509     doTestTrimTrailingFrom("x-x", "x-x");
510     doTestTrimTrailingFrom("---", "");
511     doTestTrimTrailingFrom("--x-", "--x");
512     doTestTrimTrailingFrom("--xx", "--xx");
513     doTestTrimTrailingFrom("-x--", "-x");
514     doTestTrimTrailingFrom("-x-x", "-x-x");
515     doTestTrimTrailingFrom("-xx-", "-xx");
516     doTestTrimTrailingFrom("x--x", "x--x");
517     doTestTrimTrailingFrom("x-x-", "x-x");
518     doTestTrimTrailingFrom("x-xx", "x-xx");
519     doTestTrimTrailingFrom("x-x--xx---x----x", "x-x--xx---x----x");
520     // additional testing using the doc example
521     assertEquals("abacat", anyOf("ab").trimTrailingFrom("abacatbab"));
522   }
523 
524   private void doTestTrimTrailingFrom(String in, String out) {
525     // Try a few different matchers which all match '-' and not 'x'
526     assertEquals(out, is('-').trimTrailingFrom(in));
527     assertEquals(out, is('-').or(is('#')).trimTrailingFrom(in));
528     assertEquals(out, isNot('x').trimTrailingFrom(in));
529     assertEquals(out, is('x').negate().trimTrailingFrom(in));
530     assertEquals(out, anyOf("-#").trimTrailingFrom(in));
531     assertEquals(out, anyOf("-#123").trimTrailingFrom(in));
532   }
533 
534   public void testTrimAndCollapse() {
535     // collapsing groups of '-' into '_' or '-'
536     doTestTrimAndCollapse("", "");
537     doTestTrimAndCollapse("x", "x");
538     doTestTrimAndCollapse("-", "");
539     doTestTrimAndCollapse("x-", "x");
540     doTestTrimAndCollapse("-x", "x");
541     doTestTrimAndCollapse("--", "");
542     doTestTrimAndCollapse("x--", "x");
543     doTestTrimAndCollapse("--x", "x");
544     doTestTrimAndCollapse("-x-", "x");
545     doTestTrimAndCollapse("x-x", "x_x");
546     doTestTrimAndCollapse("---", "");
547     doTestTrimAndCollapse("--x-", "x");
548     doTestTrimAndCollapse("--xx", "xx");
549     doTestTrimAndCollapse("-x--", "x");
550     doTestTrimAndCollapse("-x-x", "x_x");
551     doTestTrimAndCollapse("-xx-", "xx");
552     doTestTrimAndCollapse("x--x", "x_x");
553     doTestTrimAndCollapse("x-x-", "x_x");
554     doTestTrimAndCollapse("x-xx", "x_xx");
555     doTestTrimAndCollapse("x-x--xx---x----x", "x_x_xx_x_x");
556   }
557 
558   private void doTestTrimAndCollapse(String in, String out) {
559     // Try a few different matchers which all match '-' and not 'x'
560     for (char replacement : new char[] { '_', '-' }) {
561       String expected = out.replace('_', replacement);
562       assertEqualsSame(expected, in, is('-').trimAndCollapseFrom(in, replacement));
563       assertEqualsSame(expected, in, is('-').or(is('#')).trimAndCollapseFrom(in, replacement));
564       assertEqualsSame(expected, in, isNot('x').trimAndCollapseFrom(in, replacement));
565       assertEqualsSame(expected, in, is('x').negate().trimAndCollapseFrom(in, replacement));
566       assertEqualsSame(expected, in, anyOf("-").trimAndCollapseFrom(in, replacement));
567       assertEqualsSame(expected, in, anyOf("-#").trimAndCollapseFrom(in, replacement));
568       assertEqualsSame(expected, in, anyOf("-#123").trimAndCollapseFrom(in, replacement));
569     }
570   }
571 
572   public void testReplaceFrom() {
573     assertEquals("yoho", is('a').replaceFrom("yaha", 'o'));
574     assertEquals("yh", is('a').replaceFrom("yaha", ""));
575     assertEquals("yoho", is('a').replaceFrom("yaha", "o"));
576     assertEquals("yoohoo", is('a').replaceFrom("yaha", "oo"));
577     assertEquals("12 &gt; 5", is('>').replaceFrom("12 > 5", "&gt;"));
578   }
579 
580   public void testPrecomputedOptimizations() {
581     // These are testing behavior that's never promised by the API.
582     // Some matchers are so efficient that it is a waste of effort to
583     // build a precomputed version.
584     CharMatcher m1 = is('x');
585     assertSame(m1, m1.precomputed());
586     assertSame(m1.toString(), m1.precomputed().toString());
587 
588     CharMatcher m2 = anyOf("Az");
589     assertSame(m2, m2.precomputed());
590     assertSame(m2.toString(), m2.precomputed().toString());
591 
592     CharMatcher m3 = inRange('A', 'Z');
593     assertSame(m3, m3.precomputed());
594     assertSame(m3.toString(), m3.precomputed().toString());
595 
596     assertSame(CharMatcher.NONE, CharMatcher.NONE.precomputed());
597     assertSame(CharMatcher.ANY, CharMatcher.ANY.precomputed());
598   }
599 
600   static void checkExactMatches(CharMatcher m, char[] chars) {
601     Set<Character> positive = Sets.newHashSetWithExpectedSize(chars.length);
602     for (int i = 0; i < chars.length; i++) {
603       positive.add(chars[i]);
604     }
605     for (int c = 0; c <= Character.MAX_VALUE; c++) {
606       assertFalse(positive.contains(new Character((char) c)) ^ m.matches((char) c));
607     }
608   }
609 
610   static char[] randomChars(Random rand, int size) {
611     Set<Character> chars = new HashSet<Character>(size);
612     for (int i = 0; i < size; i++) {
613       char c;
614       while (true) {
615         c = (char) rand.nextInt(Character.MAX_VALUE - Character.MIN_VALUE + 1);
616         if (!chars.contains(c)) {
617           break;
618         }
619       }
620       chars.add(c);
621     }
622     char[] retValue = new char[chars.size()];
623     int i = 0;
624     for (char c : chars) {
625       retValue[i++] = c;
626     }
627     Arrays.sort(retValue);
628     return retValue;
629   }
630 
631   public void testToString() {
632     assertToStringWorks("CharMatcher.NONE", CharMatcher.anyOf(""));
633     assertToStringWorks("CharMatcher.is('\\u0031')", CharMatcher.anyOf("1"));
634     assertToStringWorks("CharMatcher.isNot('\\u0031')", CharMatcher.isNot('1'));
635     assertToStringWorks("CharMatcher.anyOf(\"\\u0031\\u0032\")", CharMatcher.anyOf("12"));
636     assertToStringWorks("CharMatcher.anyOf(\"\\u0031\\u0032\\u0033\")",
637         CharMatcher.anyOf("321"));
638     assertToStringWorks("CharMatcher.inRange('\\u0031', '\\u0033')",
639         CharMatcher.inRange('1', '3'));
640   }
641 
642   private static void assertToStringWorks(String expected, CharMatcher matcher) {
643     assertEquals(expected, matcher.toString());
644     assertEquals(expected, matcher.precomputed().toString());
645     assertEquals(expected, matcher.negate().negate().toString());
646     assertEquals(expected, matcher.negate().precomputed().negate().toString());
647     assertEquals(expected, matcher.negate().precomputed().negate().precomputed().toString());
648   }
649 }
650